package com.zdya.combatdocuments.p2pNetwork;

import cn.hutool.core.io.FileUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zdya.combatdocuments.component.CombatDocumentConfig;
import com.zdya.combatdocuments.constant.Constants;
import com.zdya.combatdocuments.entity.*;
import com.zdya.combatdocuments.mapper.ChainDataMapper;
import com.zdya.combatdocuments.mapper.GradeMapper;
import com.zdya.combatdocuments.util.DmcUtils;
import com.zdya.combatdocuments.util.SerialNumberUtil;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
import org.java_websocket.WebSocket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Data
@Component
@Slf4j
public class NodeUtil {

    @Autowired
    GradeMapper gradeMapper;
    @Autowired
    ChainDataMapper chainDataMapper;

    @ApiModelProperty(value = "当前在线node列表")
    private List<Node> nodeList = new CopyOnWriteArrayList<Node>();

    @ApiModelProperty(value = "当前在线id-ip列表")
    private List<IdToIp> idToIpList = new CopyOnWriteArrayList<IdToIp>();

    @ApiModelProperty(value = "当前在线用户列表")
    private List<User> userList = new CopyOnWriteArrayList<User>();

    @ApiModelProperty(value = "首次获取用户公钥临时维护列表")
    private List<User> ip_keyList = new CopyOnWriteArrayList<User>();

    @ApiModelProperty(value = "当前网络用户")
    private User localUser = new User();

    @ApiModelProperty(value = "当前网络主节点Ip")
    private String primaryIp = new String();

    @ApiModelProperty(value = "当前网络id")
    private Long networkId = null;

    @ApiModelProperty(value = "当前网络")
    private Network localNetwork = new Network();

    @ApiModelProperty(value = "当前浏览器客户端")
    private WebSocket browserClient = null;

    @ApiModelProperty(value = "当前浏览器客户端")
    public static WebSocket client = null;

    @ApiModelProperty(value = "当前区块高度")
    public static String blockHeight = null;

    public static Map<String,String> map = null;

    @ApiModelProperty(value = "当前阶段")
    private Grade grade = new Grade();

    @ApiModelProperty(value = "当前阶段所处位置标志")
    private int gradeIndex = 0;

    @ApiModelProperty(value = "当前网络所有阶段")
    private List<Grade> gradeList = new ArrayList<Grade>();

    @Value("${localNode.port}")
    private int port;

    @Value("${server.port}")
    private int serverPort;

    @ApiModelProperty(value = "是否清除数据")
    public static boolean isClear = false;

    public static ExecutorService threadPool = Executors.newFixedThreadPool(400);

    @ApiModelProperty(value = "当前缓存上链信息")
    private List<ChainData> chainDataList = new ArrayList<ChainData>();

    @ApiModelProperty(value = "数据更新时间")
     private  long chainDataTime;

    @ApiModelProperty(value = "上次同步数据时数据更新时间")
    private long syncDataTime;


    public synchronized void updateChainDataTime(long time){
        this.setChainDataTime(time);
    }

    public synchronized void addChainDataList(ChainData chainData){
        this.chainDataList.add(chainData);
    }


    /**
     * 发送消息
     * @param ws
     * @param message
     */
    public void write(WebSocket ws, String message) {
        try{
            String toIp = getIpByWs(ws);
            if (toIp == "127.0.0.1" || (toIp).equals("127.0.0.1")) {
                ws.send(message);
            }else {
                //本机私钥加密生成sign，sign+content再用接收方公钥加密生成sign2
                String localPrivateKey = localUser.getPrivateKey();
                String receivePublicKey = getUserByWs(ws).getPublicKey();
                //本机私钥签名
                SM2 sm2 = new SM2(localPrivateKey, null);
                byte[] sign = sm2.sign(message.getBytes());
                //对方公钥加密
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("sign",Base64.getEncoder().encodeToString(sign));
                jsonObject.put("message",message);
                String publicEncrypt = SmUtil.sm2(null, receivePublicKey).encryptBcd(jsonObject.toString(), KeyType.PublicKey);
                ws.send(publicEncrypt);
            }
        }catch (Exception e){
        }
    }

    /**
     *http调用--发送文件
     * @param id 接受方ip
     * @param url 文件保存返回url地址
     */
    public void writeFile(Long id,String url,Integer fileType) throws Exception{
        try{
            log.info("发送给用户id为：" +id + "的p2p消息--文件url地址:" + url);
            WebSocket ws = getWebSocketById(id,this.getIdToIpList());
            byte[] bytes = FileUtil.readBytes(url);
            String file = Base64.getEncoder().encodeToString(bytes);
            String fileName = Paths.get(url).getFileName().toString();

            JSONObject jsonObject = new JSONObject();
            jsonObject.put("type",fileType);
            jsonObject.put("content",file);
            jsonObject.put("filename",fileName);
            write(ws,jsonObject.toString());
        }catch (Exception e){
        }

    }

    /**
     *http调用--发送文件
     * @param id 接受方ip
     * @param url 文件保存返回url地址
     */
    public void writeFile(Long id, String url, Integer fileType, UserChat userChat,Document document , String sender, List<String> toAccount) throws Exception{
        try{
            log.info("发送给用户id为：" +id + "的p2p消息--文件url地址:" + url);
            WebSocket ws = getWebSocketById(id,this.getIdToIpList());
            byte[] bytes = FileUtil.readBytes(url);
            String file = Base64.getEncoder().encodeToString(bytes);
            String fileName = Paths.get(url).getFileName().toString();

            JSONObject jsonObject = new JSONObject();
            jsonObject.put("type",fileType);
            jsonObject.put("content",file);
            jsonObject.put("filename",fileName);
            jsonObject.put("userChat",userChat);
            jsonObject.put("document",document);
            jsonObject.put("sender",sender);
            jsonObject.put("toAccount",toAccount);
            write(ws,jsonObject.toString());
            /**如果接收方和发送方不是主节点，则转发至主节点*/
            String ipByWs = getIpByWs(ws);
            if(ipByWs != primaryIp && !(ipByWs).equals(primaryIp) && localUser.getIp()!=primaryIp && !(localUser.getIp()).equals(primaryIp)){
                jsonObject.put("type",7);
                write(getWebSocketByIp(primaryIp,nodeList),jsonObject.toString());
            }
        }catch (Exception e){
        }
    }

    /**
     * 从节点上链时向主节点发送信息
     */
    public void sendDataToPrimary(String serialNum,JSONObject jsonObject){
        /**如果接收方和发送方不是主节点，则转发至主节点*/
        if(localUser.getIp()!=primaryIp && !(localUser.getIp()).equals(primaryIp)){
            jsonObject.put("docType",jsonObject.getInt("type"));
            jsonObject.put("serialNum",serialNum);
            jsonObject.put("type",12);
            write(getWebSocketByIp(primaryIp,nodeList),jsonObject.toString());
        }else {
            ChainData chainData = new ChainData();
            chainData.setNetworkId(this.networkId);
            chainData.setOriginalFileName(jsonObject.getStr("name"));
            chainData.setSender(jsonObject.getStr("sender"));
            chainData.setTime(jsonObject.getLong("time"));
            chainData.setStatus(jsonObject.getLong("status"));
            chainData.setSendTime(jsonObject.getLong("sendTime"));
            chainData.setStage(jsonObject.getInt("stage"));
            chainData.setLevel(jsonObject.getStr("level"));
            chainData.setType(jsonObject.getInt("type"));
            chainData.setSerialNum(serialNum);
            chainData.setUpdateTime(System.currentTimeMillis());
            chainDataMapper.insert(chainData);
//            this.chainDataList.add(chainData);
            this.addChainDataList(chainData);
            this.updateChainDataTime(System.currentTimeMillis());
        }
    }

    /**
     * 从节点更新时向主节点发送信息
     */
    public void updateDataToPrimary(String serialNum,JSONObject jsonObject){
        /**如果发送方不是主节点，则转发至主节点*/
        if(localUser.getIp()!=primaryIp && !(localUser.getIp()).equals(primaryIp)){
            jsonObject.put("docType",jsonObject.getInt("type"));
            jsonObject.put("serialNum",serialNum);
            jsonObject.put("type",13);
            write(getWebSocketByIp(primaryIp,nodeList),jsonObject.toString());
        }else {
            for (ChainData chainData : this.chainDataList) {
                if (chainData.getSerialNum().equals(serialNum) || chainData.getSerialNum() == serialNum) {
                    chainData.setTime(jsonObject.getLong("time"));
                    chainData.setStatus(jsonObject.getLong("status"));
                    chainData.setSigns(jsonObject.getStr("signs"));
                    chainDataMapper.updateById(chainData);
                    this.updateChainDataTime(System.currentTimeMillis());
                }
            }
        }
    }

    /**
     * 获取本机ip地址
     * @return
     */
    public String getLocalIp(){
        JSONObject jsonObject = new JSONObject(DmcUtils.getIpAndGateway().get(0));
        String ip = jsonObject.getStr("ip");
        return ip;
    }

    /**
     * 返回socket的Ip地址
     */
    public String getIpByWs(WebSocket ws){
        String wsIp = ws.getRemoteSocketAddress().getAddress().toString();
        String ip = wsIp.substring(1);
        return ip;
    }

    /**
     * 根据ip地址识别websocket
     * @param nodeList
     * @return
     */
    public WebSocket getWebSocketByIp(String ipAddress, List<Node> nodeList){
        WebSocket webSocket = null;
        for(Node node: nodeList){
            if(node.getIp().equals(ipAddress)){
                webSocket = node.getWebSocket();
            }
        }
        return webSocket;
    }

    /**
     * 根据ip地址识别websocket
     * @return
     */
    public WebSocket getWebSocketById(Long id, List<IdToIp> list){
        WebSocket webSocket = null;
        for(IdToIp idToIp: list){
            if(idToIp.getUserId().equals(id) || id == idToIp.getUserId()){
                webSocket = idToIp.getWebSocket();
            }
        }
        return webSocket;
    }

    public Long getIdByWebSocket(WebSocket webSocket, List<IdToIp> list){
        Long id = null;
        for(IdToIp idToIp: list){
            if(idToIp.getWebSocket().equals(webSocket)){
                id = idToIp.getUserId();
            }
        }
        return id;
    }

    public User getUserByWs(WebSocket webSocket){
        User result = null;
        String ipByWs = getIpByWs(webSocket);
        while (result == null){
            for(User user: getIp_keyList()){
                if (ipByWs == user.getIp() || (ipByWs).equals(user.getIp())){
                    result = user;
                }
            }
            try{
                if(result == null){
                    log.info("等待1s，获取对方节点公钥中......");
                    Thread.sleep(1000);
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return result;
    }





    /**
     * 全网广播消息
     * @param message
     */
    public synchronized void broadcast(String message) {

        JSONObject content = new JSONObject();
        content.put("date",System.currentTimeMillis());
        content.put("message",message);
        content.put("sendName",localUser.getName());

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("type",5);
        jsonObject.put("content",content);

        List<Node> nodesList = this.getNodeList();
        if (CollectionUtils.isEmpty(nodesList)) {
            return;
        }
        log.info("======全网广播消息开始：");
        for(Node node : nodesList){
            this.write(node.getWebSocket(), jsonObject.toString());
        }
        log.info("======全网广播消息结束");
    }

    /**
     * 广播两个表信息
     * @param message
     */
    public synchronized void updateTableInfo(String message){
        if (CollectionUtils.isEmpty(getNodeList())) {
            return;
        }
        log.info("======全网广播表内容开始：");
        for(Node node : getNodeList()){
            String ip = node.getIp();
            if(!(ip).equals("127.0.0.1") || ip != "127.0.0.1"){
                this.write(node.getWebSocket(), message);
            }
        }
        log.info("======全网广播表内容结束");
    }

    /**
     * pdf写入鉴权信息
     * @param multipartFile
     * @param uuId
     * @return
     */
    public String writePdfInfo(MultipartFile multipartFile,String uuId,String authorizeInfo){
        try {
            //判断文件夹是否创建
            String profilePath = CombatDocumentConfig.getProfile() + File.separator+ Constants.DOC_PREFIX_PATH;
            File file = new File(profilePath);
            if (!file.exists()) {
                file.mkdirs();
            }
            String rootFilePath = "";
            rootFilePath = profilePath + File.separator+ uuId + ".pdf";
            // 打开 PDF 文件
            PDDocument document = PDDocument.load(multipartFile.getInputStream());
            // 获取文档信息对象
            PDDocumentInformation documentInformation = document.getDocumentInformation();
            // 设置鉴权信息
            //todo:对authorizeInfo进行对称加密，写入密文
            documentInformation.setCustomMetadataValue("Authorization", authorizeInfo);
            // 更新 PDF 文件
            document.save(rootFilePath);
            System.out.println("鉴权信息已写入");
            document.close();

            return rootFilePath;
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    /**
     * pdf读取鉴权信息
     * @param multipartFile
     * @return
     */
    public String readPdfInfo(MultipartFile multipartFile){
        try {
            PDDocument document = PDDocument.load(multipartFile.getInputStream());
            // 获取文档信息对象
            PDDocumentInformation documentInformation = document.getDocumentInformation();
            // 设置鉴权信息
            String authorization = documentInformation.getCustomMetadataValue("Authorization");
            //todo:对authorizeInfo进行解密，原文返回
            // 更新 PDF 文件
            System.out.println("鉴权信息已读取");
            document.close();
            return authorization;
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    /**
     * 根据当前时间设置当前阶段&阶段列表&阶段标志
     */
    public void setupGrade(){
        long currentTimeMillis = System.currentTimeMillis();
        LambdaQueryWrapper<Grade> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Grade::getNetworkId, this.getNetworkId()).orderByAsc(Grade::getCreateDate);
        List<Grade> gradeList = gradeMapper.selectList(wrapper);
        Grade localGrade = gradeList.get(gradeList.size() - 1);
        Integer index = gradeList.size() - 1;
        for(int i = 0;i<gradeList.size();i++){
            if (currentTimeMillis > gradeList.get(i).getGradeTime().longValue()) {
                continue;
            } else {
                localGrade = gradeList.get(i);
                index = i;
                break;
            }
        }
        this.setGradeIndex(index);
        this.setGrade(localGrade);
        this.setGradeList(gradeList);
    }


    /**
     * 获取授权mac
     * @return
     */
    public Map<String, String> getAuthenticationInfo() {
        Map<String, String> map = new HashMap<>();
        String authorMac = "";
        String authorCpu = "";
        String authorName = "";

        String localMacAddress = "";
        String localCpu = "";
        String localName = "";
        try {
            String decrypt = com.zdya.combatdocuments.util.FileUtil.ReadTxt(this.getLocalUser().getUsername());
            com.alibaba.fastjson.JSONObject object = com.alibaba.fastjson.JSONObject.parseObject(decrypt);
            authorMac = Objects.isNull(object.get("authorMac")) ? "" : object.get("authorMac").toString();
            authorName = Objects.isNull(object.get("authorName")) ? "": object.get("authorName").toString();
            authorCpu = Objects.isNull(object.get("authorCpu")) ? "" : object.get("authorCpu").toString();
            log.info("authorMac{},authorName{},authorCpu{}",authorMac,authorName,authorCpu);


            Map<String, String> allSn = SerialNumberUtil.getAllSn();
            localCpu = allSn.get("cpuid");
            localMacAddress = DmcUtils.getLocalMacAddress();
            localName = this.localUser.getName();
        } catch (Exception e) {

        }
        map.put("authorMac", authorMac);
        map.put("authorCpu", authorCpu);
        map.put("authorName", authorName);

        map.put("localMacAddress", localMacAddress);
        map.put("localCpu", localCpu);
        map.put("localName", localName);
        return map;
    }



}
